﻿/**
 * This class handles the control of the Kinect - it monitors it's current state
 * and includes methods for connecting and disconnecting it as well as enabling
 * skeletal tracking
 **/

using Microsoft.Kinect;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Speech;
using Microsoft.Speech.Recognition;
using System.Threading;
using System.IO;
using Microsoft.Speech.AudioFormat;
using System.Windows;


namespace EHU_InterfaceProgramming
{
    class KinectControl
    {
        public KinectSensor sensor;
        
        private SpeechRecognitionEngine sre;
        // variables for gesture detection managment
        circleGestureParts circlechecker = new circleGestureParts();
        private Boolean[] circleParts = new Boolean[4];
        pushUpGestureParts pushUpChecker = new pushUpGestureParts();
        private Boolean[] pushUpParts = new Boolean[3];
        armsCrossedParts armsCrossedChecker = new armsCrossedParts();
        private Boolean[] armsCrossedParts = new Boolean[2];

        // Keeps reference to the person trackerd
        PersonTracker personTracker;

        // Keep reference of the mainwindow object
        MainWindow window;

        public KinectControl(MainWindow window)
        {
            // Assign the window reference to the parameter passed here
            // DO NOT CHANGE THIS ORDER TO PREVENT NULL REFERENCE
            this.window = window;

            initiate_kinect();

            personTracker = new PersonTracker(window, this.sensor);

            // Initialise the tracking of people
            initPeople();

            // Initialise speech recognition
            initializeSpeech();
        }


        /**
         * This method initialises the Kinect sensor and attempts to connect to the streat
         **/
        public void initiate_kinect()
        {
            Console.WriteLine("Initialising Kinect");
            Logger.Log("Initialising Kinect...");

            try
            {
                // checking for multiple kinect sensors and accessing first one
                if (KinectSensor.KinectSensors.Count > 0)
                {
                    sensor = KinectSensor.KinectSensors[0];
                }

                if (sensor == null)
                {
                    throw new System.ArgumentException("Kinect not found");
               //     return; 
                }

                //check status of kinect
                if (sensor.Status == KinectStatus.Connected)
                {
                    Logger.Log("Kinect initiaited. Enabling sensors");

                    sensor.ColorStream.Enable();
                    sensor.DepthStream.Enable();
                    sensor.SkeletonStream.Enable();

                    Logger.Log("All frames ready! Calling sensor.start...");

                    // start skeleton tracking
                    sensor.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(sensor_AllFramesReady);
                    sensor.Start();
                    window.onKinectInitialise();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR");
                Console.WriteLine(ex.StackTrace);

                Logger.Log("Error occured: " + ex.StackTrace);
            }
        }

        /**
        * Return true if a Kinect camera is connected otherwise 0.
        * */
        public Boolean isKinectConnected()
        {
            if (KinectSensor.KinectSensors.Count > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }


        /**
         * This is called after the sensor has initialised to initialise all of the people
         * which registers them for skeleton tracking
         * */
        public void initPeople()
        {
            Logger.Log("Initialising people..");
            //Load People
            personTracker.initialisePerson(0);
            personTracker.initialisePerson(1);
        }

        /**
        * This method will disable the kinect when it closes down
        * by disabling all of the sensors and components 
        **/
        public void disable_kinect()
        {
            if (sensor == null) {
                return;
            }
            sensor.ColorStream.Disable();
            sensor.DepthStream.Disable();
            sensor.SkeletonStream.Disable();
            sensor.Stop();
        }


        /**
         * This method is called when all of the frame have initialized for
         * the Kinect camera.
         */
        public void sensor_AllFramesReady(object sender, AllFramesReadyEventArgs e)
        {
            using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
            {

                if (colorFrame == null)
                {
                    return;
                }

                byte[] pixels = new byte[colorFrame.PixelDataLength];
                colorFrame.CopyPixelDataTo(pixels);
                int stride = colorFrame.Width * 4;
                window.imgVideo.Source = BitmapSource.Create(colorFrame.Width, colorFrame.Height, 96, 96, PixelFormats.Bgr32, null, pixels, stride);
            }

            personTracker.initialiseSkeletonDrawing(e);
            checkForPushUp(personTracker.person1);
            checkForCircle(personTracker.person1);
        }
        /// <summary>
        /// Checks for the current position in hte arms crossed gesture, and passes the gesture back to mainWindow 
        /// if it is detected.
        /// </summary>
        /// <param name="pe">The Person to check.</param>
        private void checkForArmCross(Person pe)
        {
             if (armsCrossedParts[0] == true)
            {
                if (armsCrossedParts[1] == true)
                {
                            if (armsCrossedChecker.part2(pe) == true)
                            {
                                armsCrossedParts[1] = true;
                            }
                    }
                }
                else if (armsCrossedParts[0] == false)
                {
                    if (armsCrossedChecker.part1(pe) == true)
                    {
                        armsCrossedParts[0] = true;
                    }
                }
            

            if (armsCrossedParts[1] == true && armsCrossedParts[0] == true)
            {
                Logger.Log("Push Up Detected");
                Logger.Log("$Push Up");
            }
        }
        
        /// <summary>
        /// Checks for the current position on the gesture. If the gesture is completed, the name of the gesture is passed
        /// back to main window for processing.
        /// </summary>
        /// <param name="pe"></param>
        private void checkForPushUp(Person pe)
        {
            if (pushUpParts[0] == true)
            {
                if (pushUpParts[1] == true)
                {
                    if (pushUpParts[2] == true)
                    {
                            if (pushUpChecker.part3(pe) == true)
                            {
                                pushUpParts[2] = true;
                            }
                        }

                    }
                    else if (pushUpParts[1] == false)
                    {
                        if (pushUpChecker.part2(pe) == true)
                        {
                            pushUpParts[1] = true;
                        }
                    }
                }
                else if (pushUpParts[0] == false)
                {
                    if (pushUpChecker.part1(pe) == true)
                    {
                        pushUpParts[0] = true;
                    }
                }
            

            if (pushUpParts[2] == true && pushUpParts[1] == true && pushUpParts[0] == true)
            {
                Logger.Log("Push Up Detected");
                Logger.Log("$Push Up");
            }
        }
        /// <summary>
        /// Checks for the circle gesture. if the gesture is completed passes the name of the gesture back to hte main
        /// window for processing.
        /// </summary>
        /// <param name="pe"></param>
        private void checkForCircle(Person pe)
        {
            if (circleParts[0] == true)
            {
                if (circleParts[1] == true)
                {
                    if (circleParts[2] == true)
                    {
                        if (circleParts[3] == false)
                        {
                            if (circlechecker.part4(pe) == true)
                            {
                                circleParts[3] = true;
                            }
                        }

                    }
                    else if (circleParts[2] == false)
                    {
                        if (circlechecker.part3(pe) == true)
                        {
                            circleParts[2] = true;
                        }
                    }
                }
                else if (circleParts[1] == false)
                {
                    if (circlechecker.part2(pe) == true)
                    {
                        circleParts[1] = true;
                    }
                }
            }
            else if (circleParts[0] == false)
            {
                if (circlechecker.part1(pe) == true)
                {
                    circleParts[0] = true;

                }
            }
            if (circleParts[3] == true && circleParts[2] ==true && circleParts[1] == true && circleParts[0] == true)
            {
                Logger.Log( "Circle Detected");
                Logger.Log("$Circle");
            }
        }


        /**
         * These methods handle speech recognition
         * */
        private void initializeSpeech()
        {
            RecognizerInfo ri = GetKinectRecognizer();
             sre = new SpeechRecognitionEngine(ri.Id);

            //get commands list 
            var commands = getChoices();

            //culture support i.e. fr for french 
            var gb = new GrammarBuilder();
            gb.Culture = ri.Culture;
            gb.Append(commands);
            //load culture into grammer 
            var g = new Grammar(gb);
            //load grammer into engine 
            sre.LoadGrammar(g);

            //load in event handler for commands 
            sre.SpeechRecognized += new
            EventHandler<SpeechRecognizedEventArgs>(Kinect_SpeechRecognized);
            //initiate listening 
            Thread audioThread = new Thread(startAudioListening);
            audioThread.Start();

            Logger.Log("Audio Thread Started");
        }
        
        private static RecognizerInfo GetKinectRecognizer()
        {
            Func<RecognizerInfo, bool> matchingFunc = r =>
            {
                string value;
                r.AdditionalInfo.TryGetValue("Kinect", out value); return "True".Equals(value, StringComparison.InvariantCultureIgnoreCase) && "en-US".Equals(r.Culture.Name, StringComparison.InvariantCultureIgnoreCase);
            };
            return
            SpeechRecognitionEngine.InstalledRecognizers().Where(matchingFunc).FirstOrDefault();
        }
        
        private void startAudioListening()
        {
            if (sensor == null)
            {
                return;
            }
            var audioSource = sensor.AudioSource;
            audioSource.AutomaticGainControlEnabled = false;

            Stream aStream = audioSource.Start();
            sre.SetInputToAudioStream(aStream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            sre.RecognizeAsync(RecognizeMode.Multiple);
        }

        public void Kinect_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)
        {
            string output = e.Result.Text.ToLower();
            float confidence = e.Result.Confidence;
            if (output == "save" && confidence >= 0.85)
            {
                Logger.Log("User said save - saving the file.");

                window.output.saveCodeToFile();
            }
        }


        public Choices getChoices()
        {
            var choices = new Choices();
            choices.Add("save");// listening for computer
            Logger.Log("Audio choices");
            return choices;
        }
    }
}
